Skip to content

chore(tooling): census the workspace test-tsconfig divergence matrix (objectui#8714) - #8720

Merged
os-justin merged 2 commits into
mainfrom
claude/issue-8714-test-tsconfig-parity-census
Sep 9, 2026
Merged

chore(tooling): census the workspace test-tsconfig divergence matrix (objectui#8714)#8720
os-justin merged 2 commits into
mainfrom
claude/issue-8714-test-tsconfig-parity-census

Conversation

@os-justin

Copy link
Copy Markdown
Collaborator

Part of #8714 — the census the card asked for. The instance it names is not fixed here, deliberately; see The one-line fix below.

The number that decides the card

test type-programs in the workspace 42
…with a dedicated tsconfig.test.json (this census) 38
…fusing tests into the package's ordinary tsconfig.json 4 (apps/console, data-objectstack, cli, test-support)
program root files across the 38 2,477
of the 38, compiling green today 38 / 38 (measured, exit 0, 0 errors each)
divergent axes across all 38 23
behavioural axes within the 35-package sibling cohort 4types (6 values), lib (4), include (3), exclude (2)
files whose jest-dom matchers are typed only by leakage from another file 83, across 4 packages

So it is not "two packages differ on one key" — it is 35 siblings, four behavioural axes, none with fewer than two values and one with six.

What lands

  • scripts/tsconfig-test-parity-census.mjs (pnpm census:tsconfig-test-parity) — resolves every tsconfig.test.json through TypeScript's own config resolver, so extends chains flatten exactly the way tsc flattens them, and groups the projects by effective value per option. Report-only; it never fails on a divergence. The one thing it refuses is a silent zero: an empty population exits non-zero, because a census blind to its own corpus is indistinguishable from a repo whose test programs agree.
  • docs/audits/2026-09-test-tsconfig-parity-census.md — the matrix, LATENT/LIVE per divergence, a deliberate-vs-accidental verdict per row with its reason, and a proposed gate.
  • scripts/__tests__/tsconfig-test-parity-census.test.ts — 8 pins, including the emptiness leg.

No tsconfig is edited and no gate is added. Several divergences are deliberate and load-bearing; flattening them would be a repo-wide behaviour change dressed as tidying. What a gate should assert is written down in the audit; implementing it is a separate card.

Two axes that are not in any tsconfig

A diff of the config text cannot produce these, which is why three cards each found "the" divergence and none found the set:

  1. jest-dom by leakage. import '@testing-library/jest-dom' augments the matchers program-wide. plugin-detail (40 files), plugin-dashboard (16), fields (15) and plugin-list (12) type their matchers only because a different file in the same package imports it. Deleting the last importing file from plugin-detail turns 40 files TS2339 in one commit, and nothing names the dependency.
  2. Node globals by triple-slash. sdui-parser and react-runtime both leave types unset; process resolves in one and is TS2591 in the other. --explainFiles names the cause: a /// <reference types="node" /> on line 1 of one sdui-parser test file. It is the only one of the 38 whose Node types arrive that way.

Already paid — twice, in workarounds

Every one of the 38 programs is green, so the divergences are latent for compilation. They are live as workaround cost, which is the expensive kind because CI cannot see it:

  • plugin-timeline/src/ObjectTimeline.expandFls-7429.test.tsx:147 uses toBeTruthy() with a comment, from test(plugin-map,plugin-timeline): pin what these two contractEnvelope-6839 waits were standing on #8713, because the matcher would not type-check. Untouched here, as instructed.
  • 5 comment sites in plugin-designer document index arithmetic written instead of .at(-1). One of them generalises: ".at() type-checks nowhere in this repo"6 of the 38 programs are on ES2022, where it does. That false belief is what the absence of a census costs.

The one-line fix, and why it was not made

#8714 authorised naming jest-dom in plugin-timeline only if the census showed the omission accidental — "its siblings uniformly name jest-dom and nothing explains the exception." The condition fails: 21 of 35 cohort siblings do not name it, permissions documents its omission on purpose, and fields uses matchers in 86 files with types unset entirely. There is no uniform practice to have deviated from.

Measured for whoever rules on it (probe file, each leg restored after):

plugin-timeline types result
unset (today) matchers TS2339; process TS2591
["@testing-library/jest-dom"] — the literal one line matchers type-check; process still TS2591
["node", "@testing-library/jest-dom"] — the shape 7 siblings use green

If triage rules "fix the instance", the third row is the shape, not the first.

Verification

Preconditionpnpm build, 43/43 tasks green, before any tsc -p. An unbuilt tree gives TS2307 for every workspace import and makes all 38 type-programs look broken identically.

Baseline — all 38 projects compiled individually: exit=0 errors=0, 38/38.

Control legs — a probe file in plugin-timeline's program, both errors fired, so the zeros elsewhere are readings and not blindness:

zzprobe.test.ts(5,15): error TS2550: Property 'at' does not exist on type 'number[]'. …
zzprobe.test.ts(9,16): error TS2339: Property 'toBeInTheDocument' does not exist on type 'Assertion<HTMLDivElement>'.

The second reproduces the card's claim exactly.

Deliberate-vs-accidental, measured not read — adding src/**/*.d.ts to plugin-view's include (the shape 6 siblings carry) turns that project RED with 7 errors: its own src/global.d.ts redeclares process and collides with @types/node. The exclusion is load-bearing and undocumented. Three of the eight rows in the audit needed a tsc run to classify — you cannot read this off the config text.

Ablation on the new pin — the emptiness guard removed from the committed script; 1 failed | 7 passed, and the failure is exactly FAILS on an empty population instead of reporting a uniform repo (expected +0 to be 1). Restored, verified by git diff HEAD empty and by blob hash 0d62b3b6 matching HEAD. A second ablation dropping composite from the defaults set failed exactly the spelling-normalisation pin. An assertion never observed failing has not been tested; both were.

Gatespnpm lint:root 0 errors; pnpm type-check:scripts exit 0, with --listFiles confirming it really reads the new test file; pnpm exec vitest run scripts/ 128 files passed, 2 skipped; check-control-bytes, check-lint-coverage, check-type-check-coverage, check-changeset-presence, check-changeset-no-major, check-changeset-fixed, check-changeset-overwrite, check-doc-fence-languages, check-doc-links, check-doc-expression-carriage, check-shell-escape-residue, check-unreferenced-sources, check-bash32-floor, check-published-tsconfig-tooling-exclude — all exit 0. check-governed-queue-guard --test on the five changed paths: NOT GOVERNED.

⚠️ check:node-esm-load — NOT MEASURED, not red. It refused 19 of 37 entries on provenance: turbo shares one cache across every worktree of this checkout, so sibling agents' build artifacts replayed into this tree. It grades published dist entries and this diff touches no package source. (Turbo caches are content-addressed by input hash, so a replay implies byte-identical inputs — but the gate's own remedy is --force-build, and CI builds fresh.)

Changeset — empty frontmatter, the declared no-release form. The gate's own verdict: "No source or published contract of a released package changed in this range, so no changeset is owed."

What is left for triage

  1. Whether plugin-timeline's omission should be fixed at all, and if so in which shape.
  2. Whether the proposed gate is wanted — the audit says what it would assert and what it would have caught (this card, finding(devx): test type-checking runs at two different lib levels per package, so .at(-1) compiles in 75 test files and is TS2550 in others #8691, the false plugin-designer comment, and 83 leaning files).
  3. The plugin-designer comment that generalises falsely, and the 3 include patterns matching zero files.

None of those is done here. #8691 and #8710 are not addressed by this PR either — #8710's axis is between Vite's alias table and the exports map, not between two tsconfigs, so this instrument structurally cannot see it.

🤖 Generated with Claude Code

https://claude.ai/code/session_01YBWFb5YgMU5dw8p2VKj16S


Generated by Claude Code

…(objectui#8714)

Three per-package `tsconfig` divergences had been found — each by a developer
tripping over it, none by any instrument — and the card asked the question none
of them could answer: how many packages differ, on how many axes, with how many
distinct values each.

Adds the instrument and the reading:

- `scripts/tsconfig-test-parity-census.mjs` (`pnpm census:tsconfig-test-parity`)
  resolves every `tsconfig.test.json` through TypeScript's own config resolver,
  so `extends` chains flatten the way `tsc` flattens them, and groups the 38
  projects by their effective value on every option. Report-only: it never fails
  on a divergence, because several of them are deliberate and load-bearing.
  The one thing it refuses is a silent zero — an empty population exits
  non-zero, since a census blind to its own corpus is indistinguishable from a
  repo whose test programs agree.

- `docs/audits/2026-09-test-tsconfig-parity-census.md` records the matrix: 42
  test type-programs, 38 with a dedicated project, 2,477 program files, 23
  divergent axes, and 4 behavioural axes across the 35-package sibling cohort
  (`types` 6 values, `lib` 4, `include` 3, `exclude` 2).

It also records the two routes a diff of the config text cannot see. A bare
`import '@testing-library/jest-dom'` augments the matchers program-wide, so 83
files across four packages type-check only because a DIFFERENT file in the same
package imports it; and one `/// <reference types="node" />` gives `sdui-parser`
the Node globals its ten `types`-unset siblings do not have.

No `tsconfig` is edited and no gate is added. The audit proposes what a gate
should assert and what it would have caught; ruling on that is a separate card.
The one conditional fix the card authorised — naming jest-dom in
`plugin-timeline` — is declined with evidence: 21 of 35 siblings do not name it,
`permissions` documents its omission on purpose, and `fields` uses matchers in 86
files with `types` unset entirely, so there is no uniform practice to have
deviated from.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YBWFb5YgMU5dw8p2VKj16S
`scripts/tsconfig-test-parity-census.mjs` hand-typed its entry guard as
`process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]`,
which `check:entry-guard` rejects. Node realpaths `import.meta.url` but
leaves `argv[1]` as the caller typed it, so a script reached through a
symlink compares two different paths, answers false, and does nothing --
exit 0, no output, indistinguishable from a pass to any wrapper holding
only the exit status.

Measured on this file, before and after. Old spelling: run directly it
prints 25047 bytes of census; reached through a symlink it printed 0
bytes and exited 0. Through `isEntrypoint` both routes print the same
25047 bytes, and importing the module for its exports still prints
nothing.

The gate reported line 261 twice because it counts token occurrences
rather than guard sites -- that one line spelled `process.argv[1]` twice,
once as the truthiness leg and once as the comparand.

Refs: objectui#8714

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YBWFb5YgMU5dw8p2VKj16S
@os-justin
os-justin added this pull request to the merge queue Sep 9, 2026
Merged via the queue into main with commit f6205c1 Sep 9, 2026
33 checks passed
@os-justin
os-justin deleted the claude/issue-8714-test-tsconfig-parity-census branch September 9, 2026 02:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

configuration dependencies documentation Improvements or additions to documentation tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants